<?php

/** 
 * @class XQL_Query
 * @extends XQL_QueryPart
 *
 * Futtatható lekérések kezelésére és befogadására készített osztály.
 */
class XQL_Query extends XQL_QueryPart
{
	/**
	 * A query() PDO hívással futtatandó.
	 */
	const MODE_RUN = 1;
	
	/**
	 * Az exec() PDO hívással futtatandó.
	 */
	const MODE_EXEC = 2;
	
	/**
	 * A prepare() PDO hívással futtatandó.
	 */
	const MODE_PREPARE = 3;
	
	/**
	 * Ha a tulajdonság TRUE, a lekérés eredménye csak egy record lesz, 1D
	 * tömbben.
	 * @var bool
	 */
	protected $isResultSingleRow = FALSE;
	
	/**
	 * Ha a tulajdonság értéke TRUE, a lekérés eredménye az első rekord első
	 * mezője lesz.
	 * @var bool
	 */
	protected $isResultSingleField = FALSE;
	
	/**
	 * Ha a tulajdonság értéke TRUE, a lekérés visszatérése az utoljára
	 * kiosztott automatikus kulcs érték lesz.
	 * @var bool
	 */
	protected $isResultAutoincrement = FALSE;
	
	/**
	 * Ha a tulajdonság értéke TRUE, a lapozás támogatása be lesz kapcsolva.
	 * @var bool
	 */
	protected $isPagerRequired = FALSE;

	/**
	 * Ha a tulajdonság értéke TRUE, a lekérés futtatása
	 * {@link onBeforeExecute előtt} és {@link onAfterExecute után} egy-egy
	 * callback is meghívásra kerül, amelyek befolyásolhatják a lekérés
	 * felépítését és/vagy visszatérését.
	 * @var bool
	 */
	protected $enableCallbacks = FALSE;
	
	/**
	 * Ha a lekérés lapozottan történik, akkor egy oldalon maximum ennyi
	 * rekord fog megjelenni belőle. Ha értéke 'NULL'-tól eltér,
	 * a lapozás érvényessé válik az értékadást követő hívásokra.
	 * @var int
	 */
	public $pageRecordsCount = NULL;
	
	/**
	 * Ha a lapozás érvényes, az offset kiszámolásra kerül a megfelelő metódusok által. 
	 * @var int
	 */
	protected $pageOffset = NULL;
	
	/**
	 * Rendezési beállítások az lekérés idejére, ha értéke nem NULL.
	 * @var string
	 */
	protected $pageOrder = NULL;
	
	/**
	 * A lapozó limit objektum
	 * @var pLimitObject
	 */
	protected $pageLimitObject;

	/**
	 * A lekérések paraméter-alapú formázásához használt callback függvény
	 * neve. A callback függvény paraméterlistája: string $lekérés, array $paraméterek<br/>
	 * Ha a tulajdonság üres, a formázás nem történik meg.<br/>
	 * Alapértelmezett: {@link http://php.net/vsprintf vsprintf}()
	 * @var string
	 */
	protected $formatCallback = 'vsprintf';
	
	/**
	 * Az obektum esetleges futtatási módja. A futtató model általában
	 * tiszteletben tartja a meghatározott futási módot, így meghatározása
	 * erősen ajánlott.
	 * @var int
	 * @see XQL_Query::MODE_RUN
	 * @see XQL_Query::MODE_EXEC
	 * @see XQL_Query::MODE_PREPARE
	 */
	protected $executionMode = XQL_Query::MODE_RUN;
	
	/**
	 * Adatátalakító osztály speciális esetekre.
	 * Példányosítása és beállítása a konstruktorban ajánlott.
	 * @var XQL_DataTransformation
	 */
	protected $dataTransformation;

	/**
	 * Ha értéke TRUE, a futtatott lekérések az oldalbetöltés végén
	 * megjelennek.
	 *
	 * @var bool
	 */
	public $debug = FALSE;
	
	/**
	 * Konstruktor.
	 * Hozzárendeli az adatbáziskapcsolatot az objektumhoz és betölti a lekérést
	 * a gyorsítótárból, amennyiben rendelkezik névvel és már megtalálható ott.
	 *
	 * @param string $connectionAlias
	 * @access public
	 */
	public function __construct($connectionAlias='default')
	{
		parent::__construct($connectionAlias);

		if (isset($this->name))
		{
			if (XQL_Manager::exists($this->name))
			{
				$this->query = XQL_Manager::get($this->name);
			}
		}
	}

	/**
	 * Visszad egy példányt az osztályból.
	 *
	 * @return XQL_Query
	 */
	static public function create($connectionAlias='default')
	{
		return new XQL_Query($connectionAlias);
	} // static method
	
	/**
	 * 
	 *
	 * @return XQL_Query
	 */
	public function singleRowResult()
	{
		$this->isResultSingleRow = TRUE;
		$this->isResultSingleField = FALSE;
		return $this;
	}
	
	/**
	 * @return XQL_Query
	 */
	public function singleFieldResult()
	{
		$this->isResultSingleField = TRUE;
		$this->isResultSingleRow = FALSE;
		$this->isResultAutoincrement = FALSE;
		return $this;
	}
	
	/**
	 * @return XQL_Query
	 */
	public function normalResult()
	{
		$this->isResultSingleRow = FALSE;
		$this->isResultSingleField = FALSE;
		$this->isResultAutoincrement = FALSE;
		return $this;
	}
	
	/**
	 * @return XQL_Query
	 */
	public function autoIdResult()
	{
		$this->isResultAutoincrement = TRUE;
		$this->isResultSingleRow = FALSE;
		$this->isResultSingleField = FALSE;
		return $this;
	}
	
	/**
	 * Beállítja a lekérés futtatási módját. Ezt a módot a legtöbb model
	 * tiszteletben tartja, így használata erősen ajánlott. Hibás módválasztás
	 * esetén kivétel keletkezik.
	 *
	 * @param int $mode
	 * @return XQL_Query Sajátmaga
	 * @see XQL_Query::MODE_RUN
	 * @see XQL_Query::MODE_EXEC
	 * @see XQL_Query::MODE_PREPARE
	 */
	public function setExecMode($mode)
	{
		if
		(
			$mode == XQL_Query::MODE_RUN or
			$mode == XQL_Query::MODE_EXEC or
			$mode == XQL_Query::MODE_PREPARE
		)
		{
			$this->executionMode = $mode;
		}
		else
		{
			throw new XQL_E_Query_InvalidMode($this->name);
		}
		
		return $this;
	}
	
	/**
	 * Visszaadja a lekérés futtatási módját.
	 *
	 * @return int A futtatási mód
	 * @see XQL_Object::MODE_RUN
	 * @see XQL_Object::MODE_EXEC
	 * @see XQL_Object::MODE_PREPARE
	 * @see XQL_Object::setExecMode
	 */
	public function getExecMode()
	{
		return $this->executionMode;
	}
	
	/**
	 * Beállítja az offset-et, ha a lapozás aktív. Meghívása a
	 * lekérés előtt szükséges.
	 *
	 * @param int $page
	 */
	public function setPage($page)
	{
		if (is_null($this->pageRecordsCount))
		{
			$this->pageOffset = NULL;
		}
		
		$page == (int)$page; // Fő a biztonság
		if ($page <= 0)
		{
			$page = 1;
		}
		
		$page--;
		$this->pageOffset = $page * $this->pageRecordsCount;

		$this->pageLimitObject = XQL_Limit::create()->
			join(
				XQL_Number::create()->set($this->pageRecordsCount)
			)->
			join(
				XQL_Offset::create()->set($this->pageOffset)
			);
		
		return $this;
	} // function
	
	/**
	 * Visszatér a lapozónak szükséges adatokkal, az utoljára futtatott lekérés,
	 * az aktuális oldal (setPage) és a kapott paraméter függvényében.
	 *
	 * @param int $countAllRecords A lapozás nélkül elérhető maximális rekordszám
	 * @return array Lapozóadatok asszociatív tömbje (kulcsok: first, last, act, prev, next)
	 */
	public function getPagerData($countAllRecords)
	{
		$limit = $this->pageRecordsCount;
		$offset = $this->pageOffset;
		
		$first = 1;
		$last = ceil($countAllRecords / $this->pageItems);
		$act = ($offset / $this->pageItems) + 1;
		$prev = ($act <= $first ? $first : $act - 1);
		$next = ($act >= $last ? $last : $act + 1);
		
		return array(
			'first'	=> $first,
			'prev'	=> $prev,
			'act'	=> $act,
			'next'	=> $next,
			'last'	=> $last
		);
	} // function
	
	/**
	 * Beállítja az aktuális oldalra vonatkozó rendezési beállításokat.
	 * Ha paraméter nélkül kerül meghívásra, a beállítások törlődnek.
	 *
	 * @param $field1 XQL_Field vagy XQL_Function
	 * @param $field2 XQL_Field vagy XQL_Function
	 * @param $fieldN XQL_Field vagy XQL_Function
	 */
	public function setPageOrder()
	{
		$args = func_get_args();
		if (count($args) == 0)
		{
			$this->pageOrder = NULL;
		}
		else
		{
			$this->pageOrder = XQL_OrderBy::create()->set($args);
		}
		
		return $this;
	}

	/**
	 * Visszaadja, hogy a lekérés előállításához szükséges-e adatátalakítás.
	 *
	 * @return bool
	 */
	public function hasTransformableData()
	{
		return isset($this->dataTransformation);
	}

	/**
	 * Visszatér a lekérés objektumhoz társított adatátalakító objektummal.
	 * 
	 * @return XQL_DataTransformation
	 */
	public function getTransformer()
	{
		return $this->dataTransformation;
	}
	
	/**
	 * Lefuttatja a lekérést a beállításoknak és paramétereknek megfelelően.
	 * Meghívja az onBeforeExecute() és onAfterExecute() callback metódusokat,
	 * ha a $enableCallbacks tulajdonság értéke TRUE.
	 * 
	 * @param array $args
	 * @param bool $disableCallbakcs <b>Belső használatra!</b> Felülbírálja az
	 * {@link $enableCallbacks} beállítást
	 * @return mixed
	 */
	public function execute($args=array(), $disableCallbacks=FALSE)
	{
		if (!$disableCallbacks) $this->getQuery(TRUE);
		if ($disableCallbacks and !$this->isQueryLoaded() and !XQL_Manager::exists($this->name))
		{
			XQL_Manager::register($this->name, $this->query);
		}

		if ($this->enableCallbacks and !$disableCallbacks)
		{
			$oQuery = $this->onBeforeExecute($args);
			if (!empty($this->formatCallback) and count($args) > 0)
			{
				$this->query = call_user_func($this->formatCallback, $this->query, $args);
			}
			XQL_Manager::logQuery($this->query);
			$rAction = $oQuery->execute($args, TRUE);
			$rAction = $this->onAfterExecute($rAction, $args);
		}
		else
		{
			if (!$disableCallbacks)
			{
				if (!empty($this->formatCallback) and count($args) > 0)
				{
					$this->query = call_user_func($this->formatCallback, $this->query, $args);
				}
				XQL_Manager::logQuery($this->query);
			}
			$rAction = $this->internalExecute($args);
		}

		return $rAction;
	}

	/**
	 * Lekérés futtatása
	 * A futtatás maga több lépésben valósul meg. 
	 *
	 * @param array $args
	 * @return mixed
	 */
	protected function internalExecute($args=array())
	{
		// Lekérés törzse
		$query = $this->getQuery();
		// Order By, ha van
		if ($this->pageOrder !== NULL)
		{
			$query .= ' '.(string)$this->pageOrder;
		}
		// Limit és Offset lapozáshoz, ha van
		if ($this->pageRecordsCount !== NULL)
		{
			$query .= ' '.(string)$this->pageLimitObject;
		}
		
		if (!$this->isResultAutoincrement)
		{
			$result = $this->internalExecuteAs($query);
		}
		else
		{
			$this->internalExecuteAs($query);
			$result = $this->connection->pdo->lastInsertId();
		} // if
		
		return $result;
	}

	/**
	 * A metódusban lehetőség van <b>ugyanebből</b> az osztályból egy újabb
	 * példány létrehozására, amely példány tetszés szerint módosítható. Az
	 * {@link $enableCallbacks} beállítás TRUE értéke esetén nem a jelen
	 * objektum lekérése, hanem az itt visszaadott (módosított) lekérés fog
	 * lefutni.
	 *
	 * @param array $args Az aktuális metódus által kapott argumentumok
	 * @return XQL_Query Új, módosított lekérés objektum
	 */
	protected function onBeforeExecute($args)
	{
		// nem implementált
		return $this;
	}

	/**
	 * A metódus lehetőséget ad az {@link execute} metódus visszatérési
	 * értékének megváltoztatására, amennyiben a {@link $enableCallbacks}
	 * tulajdonság TRUE értékre van állítva.
	 *
	 * @param mixed $resultset Visszatérési halmaz
	 * @param array $args Az aktuális metódus által kapott argumentumok
	 * @return mixed
	 */
	protected function onAfterExecute($resultset, $args)
	{
		// nem implementált
		return $resultset;
	}
	
	/**
	 * Lekérdezés futtatása az adatbázis kiszolgálón, az objektumban megadott
	 * mód szerint.
	 *
	 * @return mixed
	 */
	protected function internalExecuteAs($query)
	{
		$result = FALSE;

		switch ($this->getExecMode())
		{
			case XQL_Query::MODE_RUN:
			{
				try
				{
					$stmt = $this->connection->pdo->query($query);
					if (is_object($stmt)) $result = $stmt->fetchAll(PDO::FETCH_ASSOC);
				}
				catch (PDOException $e)
				{
					throw $e;
				}
				
				if (is_array($result) and count($result) > 0)
				{
					if ($this->isResultSingleRow)
					{
						$result = $result[0];
					}
					else if ($this->isResultSingleField)
					{
						$result = $result[0];
						$result = array_shift($result);
					}
				}
				else
				{
					$result = NULL;
				}
				
				break;
			}
			case XQL_Query::MODE_PREPARE:
			{
				$result = $this->connection->pdo->prepare($query);
				break;
			}
			case XQL_Query::MODE_EXEC:
			{
				//var_dump($query);
				$result = $this->connection->pdo->exec($query);
				break;
			}
		} // switch
		
		return $result;
	}
}

?>